package design;

import java.awt.Component;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;

import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import javax.swing.border.EmptyBorder;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;

import model.ProgramGraph;
import model.TransitionSystem;
import model.UnfoldingTransitionSystem;
import model.VariableHashTable;
import net.sourceforge.jeval.EvaluationException;
import utilities.GraphCreator;
import utilities.NVSupervisorCreator;
import utilities.VSupervisorCreator;
import utilities.XMLWriter;
import exception.ExtraCharactersException;
import exception.IllegalCharacterSequenceException;
import exception.LessCharactersExeption;
import exception.NoRuleException;
import exception.UninitializedVariableException;

public class Gui extends JFrame
{
	enum Type
	{
		DETERMINISTIC, NONDETERMINISTIC
	}

	enum WithVariables
	{
		YES, NO
	}

	private static final long serialVersionUID = 5221920714548024444L;
	private static final Font normal = new Font("Arial", Font.PLAIN, 11);
	private static final Font larger = new Font("Arial", Font.PLAIN, 12);

	private ProgramGraph pGraph;

	private TransitionSystem tSystem;

	private Type type;

	private WithVariables withVariables;

	private File file, restrictionFile;

	private String[] transitions;

	private String[] vars;

	private HashMap<String, Component> componentMap;

	private JLabel lblChooseModelType;
	private JLabel lblVariables;
	private JPanel contentPane;
	private JScrollPane scrollPane2;
	private JScrollPane scrollPane1;

	/**
	 * Create the frame.
	 */
	public Gui()
	{
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setBounds(100, 100, 598, 460);

		contentPane = new JPanel();
		contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
		contentPane.setLayout(null);
		setContentPane(contentPane);

		initVariables();
		initMenuBar();
		initChooseModel();
		initWithVariables();
		initFileChooser();
	}

	/**
	 * Initializes application blocks. Used after generating the supervisor.
	 */
	public void init()
	{
		initVariables();
		initMenuBar();
		initChooseModel();
		initWithVariables();
		initFileChooser();
	}

	public void initVariables()
	{
		componentMap = new HashMap<String, Component>();
	}

	/**
	 * Creates the {@link JMenuBar}.
	 */
	public void initMenuBar()
	{
		JMenuBar menuBar = new JMenuBar();
		menuBar.setBounds(0, 0, 709, 21);
		menuBar.setName("menuBar");
		JMenu fileMenu = createJMenu("File", KeyEvent.VK_F);
		JMenuItem fileMenuExitItem = new JMenuItem("Exit");
		fileMenuExitItem.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent event)
			{
				System.exit(0);
			}
		});
		fileMenu.add(fileMenuExitItem);

		JMenu helpMenu = createJMenu("Help", KeyEvent.VK_H);
		JMenuItem helpMenuAboutItem = new JMenuItem("About");
		helpMenuAboutItem.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent event)
			{
				JOptionPane.showMessageDialog(contentPane, "version 1.4");
			}
		});
		helpMenu.add(helpMenuAboutItem);

		menuBar.add(fileMenu);
		menuBar.add(helpMenu);

		componentMap.put(menuBar.getName(), menuBar);
		contentPane.add(menuBar);
	}

	/**
	 * Creates a 'block' consisted of a {@link JLabel} and two {@link JRadioButton}s for choosing
	 * the model type.
	 */
	public void initChooseModel()
	{
		type = Type.DETERMINISTIC;

		lblChooseModelType = new JLabel("Choose model type:");
		lblChooseModelType.setName("lblChooseModelLabel");
		lblChooseModelType.setFont(larger);
		lblChooseModelType.setBounds(10, 32, 226, 14);
		contentPane.add(lblChooseModelType);

		JRadioButton rdbtnDeterministic = new JRadioButton("Deterministic");
		rdbtnDeterministic.setFont(normal);
		rdbtnDeterministic.setName("rdbtnDeterministic");
		rdbtnDeterministic.setBounds(10, 53, 109, 23);
		rdbtnDeterministic.setSelected(true);
		rdbtnDeterministic.addActionListener(new ActionListener()
		{
			@Override
			public void actionPerformed(ActionEvent e)
			{
				type = Type.DETERMINISTIC;
			}
		});
		componentMap.put(rdbtnDeterministic.getName(), rdbtnDeterministic);
		contentPane.add(rdbtnDeterministic);

		JRadioButton rdbtnNondeterministic = new JRadioButton("Non-Deterministic");
		rdbtnNondeterministic.setFont(normal);
		rdbtnNondeterministic.setName("rdbtnNondeterministic");
		rdbtnNondeterministic.setBounds(121, 53, 109, 23);
		rdbtnNondeterministic.addActionListener(new ActionListener()
		{
			@Override
			public void actionPerformed(ActionEvent arg0)
			{
				type = Type.NONDETERMINISTIC;
			}
		});
		componentMap.put(rdbtnNondeterministic.getName(), rdbtnNondeterministic);
		contentPane.add(rdbtnNondeterministic);

		ButtonGroup modelTypeGroup = new ButtonGroup();
		modelTypeGroup.add(rdbtnDeterministic);
		modelTypeGroup.add(rdbtnNondeterministic);

	}

	/**
	 * Creates a 'block' consisted of a {@link JLabel} and two {@link JRadioButton}s for choosing if
	 * the model contains variables.
	 */
	public void initWithVariables()
	{
		withVariables = WithVariables.YES;

		lblVariables = new JLabel("Does the model contain variables:");
		lblVariables.setFont(larger);
		lblVariables.setBounds(10, 101, 226, 14);
		contentPane.add(lblVariables);

		JRadioButton rdbtnYes = new JRadioButton("Yes");
		rdbtnYes.setFont(normal);
		rdbtnYes.setName("rdbtnYes");
		rdbtnYes.setSelected(true);
		rdbtnYes.setBounds(10, 122, 109, 23);
		rdbtnYes.addActionListener(new ActionListener()
		{
			@Override
			public void actionPerformed(ActionEvent e)
			{
				withVariables = WithVariables.YES;
				// scrollPane2.setVisible(true);
				((JButton) componentMap.get("openRestrictionBtn")).setVisible(true);
				contentPane.updateUI();
			}
		});
		componentMap.put(rdbtnYes.getName(), rdbtnYes);
		contentPane.add(rdbtnYes);

		JRadioButton rdbtnNo = new JRadioButton("No");
		rdbtnNo.setFont(normal);
		rdbtnNo.setName("rdbtnNo");
		rdbtnNo.setBounds(121, 122, 109, 23);
		rdbtnNo.addActionListener(new ActionListener()
		{
			@Override
			public void actionPerformed(ActionEvent e)
			{
				withVariables = WithVariables.NO;
				// scrollPane2.setVisible(false);
				((JButton) componentMap.get("openRestrictionBtn")).setVisible(false);
				contentPane.updateUI();
			}
		});
		componentMap.put(rdbtnNo.getName(), rdbtnNo);
		contentPane.add(rdbtnNo);

		ButtonGroup withVariables = new ButtonGroup();
		withVariables.add(rdbtnYes);
		withVariables.add(rdbtnNo);
	}

	/**
	 * Creates a graph from selected file. Also creates the 'create new' and 'open restriction file'
	 * button.
	 */
	public void initFileChooser()
	{
		restrictionFile = null;
		JFileChooser fileChooser = new JFileChooser();
		fileChooser.setDialogTitle("Open file");
		fileChooser.setName("fileChooser");
		fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
		componentMap.put(fileChooser.getName(), fileChooser);

		JButton chooseFileBtn = new JButton("Open file");
		chooseFileBtn.setFont(larger);
		chooseFileBtn.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent e)
			{
				int returnVal = ((JFileChooser) componentMap.get("fileChooser"))
						.showOpenDialog(Gui.this);
				switch (returnVal)
				{
				case JFileChooser.APPROVE_OPTION:
					file = ((JFileChooser) componentMap.get("fileChooser")).getSelectedFile();
					JOptionPane.showMessageDialog(contentPane, "File \"" + file.getAbsolutePath()
							+ "\" is selected.");
					if (withVariables.equals(WithVariables.YES))
					{
						try
						{
							pGraph = GraphCreator.generateGraphWithVariables(file.getAbsolutePath());
							vars = pGraph.getAllVariables().toArray(new String[0]);
							transitions = pGraph.getAllTransitions();
							initTransitionsPane();
							initDomainPane();
							scrollPane1.setVisible(true);
							scrollPane2.setVisible(true);
							contentPane.updateUI();
							initStartButton();
						}
						catch (NullPointerException | IOException
								| IllegalCharacterSequenceException | LessCharactersExeption
								| NoRuleException | ExtraCharactersException e1)
						{
							JOptionPane.showMessageDialog(contentPane, "Error!");
							// e1.printStackTrace();
						}
					}
					else
					{
						try
						{
							tSystem = GraphCreator.generateGraphWithoutVariables(file
									.getAbsolutePath());
							transitions = tSystem.getAllTransitions();
							initTransitionsPane();
							scrollPane1.setVisible(true);
							contentPane.updateUI();
							initStartButton();
						}
						catch (FileNotFoundException e1)
						{
							JOptionPane.showMessageDialog(contentPane, "Error!");
							// e1.printStackTrace();
						}
					}
					break;
				case JFileChooser.ERROR_OPTION:
					JOptionPane.showMessageDialog(contentPane, "Error choosing file.");
					break;
				}
			}
		});
		chooseFileBtn.setBounds(10, 161, 125, 25);
		contentPane.add(chooseFileBtn);

		JButton createNewBtn = new JButton("Create new");
		createNewBtn.setFont(larger);
		createNewBtn.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent e)
			{
				try
				{
					Runtime.getRuntime().exec("java -jar JFLAP.jar");
				}
				catch (IOException ex)
				{
					JOptionPane.showMessageDialog(contentPane, "Could not open JFLAP.");
				}
			}
		});
		createNewBtn.setBounds(159, 161, 125, 25);
		contentPane.add(createNewBtn);

		JButton openRestrictionBtn = new JButton("Open restriction file");
		openRestrictionBtn.setFont(larger);
		openRestrictionBtn.setName("openRestrictionBtn");
		openRestrictionBtn.setBounds(308, 161, 149, 25);
		openRestrictionBtn.setVisible(true);
		openRestrictionBtn.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent e)
			{
				int returnVal = ((JFileChooser) componentMap.get("fileChooser"))
						.showOpenDialog(Gui.this);
				switch (returnVal)
				{
				case JFileChooser.APPROVE_OPTION:
					restrictionFile = ((JFileChooser) componentMap.get("fileChooser"))
							.getSelectedFile();
					JOptionPane.showMessageDialog(contentPane,
							"File \"" + restrictionFile.getAbsolutePath() + "\" is selected.");
					break;
				case JFileChooser.ERROR_OPTION:
					JOptionPane.showMessageDialog(contentPane, "Error choosing file.");
					break;
				}
			}
		});
		componentMap.put(openRestrictionBtn.getName(), openRestrictionBtn);
		contentPane.add(openRestrictionBtn);
	}

	/**
	 * Initializes the start button which validates the domain, initial variables and checks if
	 * restriction file is selected. Starts the algorithm for creating the supervisor.
	 */
	public void initStartButton()
	{
		JButton startButton = new JButton("Start");
		startButton.setBounds(10, 330, 125, 25);
		startButton.addActionListener(new ActionListener()
		{
			@Override
			public void actionPerformed(ActionEvent arg0)
			{
				if (withVariables.equals(WithVariables.YES))
				{
					if (validateDomainAndInitialValues())
					{
						VariableHashTable initialValues = new VariableHashTable();
						VariableHashTable fromValues = new VariableHashTable();
						VariableHashTable toValues = new VariableHashTable();
						for (String var : vars)
						{
							String initialStr = ((JTextField) componentMap.get("initial_" + var))
									.getText();
							String fromStr = ((JTextField) componentMap.get("domainFrom_" + var))
									.getText();
							String toStr = ((JTextField) componentMap.get("domainTo_" + var))
									.getText();

							Integer initial = Integer.parseInt(initialStr);
							Integer from = Integer.parseInt(fromStr);
							Integer to = Integer.parseInt(toStr);

							initialValues.insertVariable(var, initial);
							fromValues.insertVariable(var, from);
							toValues.insertVariable(var, to);
						}
						pGraph.setInitialVariableValues(initialValues);
						pGraph.setVariableDomainFromTable(fromValues);
						pGraph.setVariableDomainToTable(toValues);
						pGraph.setRestrictionFilePath(restrictionFile.getAbsolutePath());

						for (String tr : transitions)
						{
							if (((JCheckBox) componentMap.get("transition_" + tr)).isSelected())
								pGraph.getTransitionHashTable().setUncontrollableTransition(tr);
						}

						try
						{
							UnfoldingTransitionSystem supervisor = VSupervisorCreator
									.createSupervisor(pGraph);
							String outputFilePath = file.getParentFile().getAbsolutePath()
									+ "/output.jff";
							XMLWriter.writeXMLDocumentForSupervisor(supervisor, outputFilePath);
						}
						catch (EvaluationException | UninitializedVariableException | IOException
								| IllegalCharacterSequenceException | LessCharactersExeption
								| ExtraCharactersException | NoRuleException
								| ParserConfigurationException | TransformerException e)
						{
							// e.printStackTrace();
							return;
						}
					}
					else
					{
						return;
					}
				}
				else
				{
					for (String tr : transitions)
					{
						if (((JCheckBox) componentMap.get("transition_" + tr)).isSelected())
							tSystem.setTransitionType(tr, false);
					}

					try
					{
						TransitionSystem supervisor;
						if (type.equals(Type.NONDETERMINISTIC))
						{
							supervisor = NVSupervisorCreator.createFromNondeterministic(tSystem);
						}
						else
						{
							supervisor = NVSupervisorCreator.createFromDeterministic(tSystem);
						}
						String outputFilePath = file.getParentFile().getAbsolutePath()
								+ "/output.jff";
						XMLWriter.writeXMLDocumentForSupervisor(supervisor, outputFilePath);
					}
					catch (Exception e)
					{
						JOptionPane.showMessageDialog(contentPane, "A supervisor doesn't exist for this model!");
						contentPane.removeAll();
						componentMap.clear();
						init();
						revalidate();
						repaint();
						return;
					}
				}
				JOptionPane.showMessageDialog(contentPane, "Output file saved in: "
						+ file.getParentFile().getAbsolutePath() + "/output.jff");
				contentPane.removeAll();
				componentMap.clear();
				init();
				revalidate();
				repaint();
			}
		});
		contentPane.add(startButton);
	}

	/**
	 * Checks if the restriction file is selected and if is, continues with validating the variables
	 * domains and initial values.
	 */
	public boolean validateDomainAndInitialValues()
	{
		if (restrictionFile == null)
		{
			JOptionPane.showMessageDialog(contentPane, "Please select the restriction file.");
			return false;
		}
		for (String var : vars)
		{
			try
			{
				String initialStr = ((JTextField) componentMap.get("initial_" + var)).getText();
				String fromStr = ((JTextField) componentMap.get("domainFrom_" + var)).getText();
				String toStr = ((JTextField) componentMap.get("domainTo_" + var)).getText();

				Integer initial = Integer.parseInt(initialStr);
				Integer from = Integer.parseInt(fromStr);
				Integer to = Integer.parseInt(toStr);

				if ((from > to) || (initial < from || initial > to))
				{
					JOptionPane.showMessageDialog(contentPane, "Variables validation error.");
					return false;
				}
			}
			catch (Exception e)
			{
				JOptionPane.showMessageDialog(contentPane, "Variables validation error.");
				return false;
			}
		}
		return true;
	}

	/**
	 * Creates the menu.
	 */
	public JMenu createJMenu(String text, int mnemonic)
	{
		JMenu menu = new JMenu(text);
		menu.setMnemonic(mnemonic);
		menu.addMouseListener(new MouseListener()
		{
			@Override
			public void mouseClicked(MouseEvent e)
			{
			}

			@Override
			public void mousePressed(MouseEvent e)
			{
			}

			@Override
			public void mouseReleased(MouseEvent e)
			{
			}

			@Override
			public void mouseEntered(MouseEvent e)
			{
				((JMenu) e.getSource()).setArmed(true);
			}

			@Override
			public void mouseExited(MouseEvent e)
			{
				((JMenu) e.getSource()).setArmed(false);
			}
		});
		return menu;
	}

	/**
	 * Initializes the transition pane.
	 */
	public void initTransitionsPane()
	{
		if (scrollPane1 != null)
		{
			contentPane.remove(scrollPane1);
			// for (Entry<String, Component> entry : componentMap.entrySet())
			// if (entry.getKey().startsWith("transition_"))
			// componentMap.remove(entry.getKey());
		}
		if (transitions.length < 1)
			return;

		scrollPane1 = new JScrollPane();
		int height = 115;
		if (transitions.length == 1)
			height = 50;
		if (transitions.length == 2)
			height = 70;
		if (transitions.length == 3)
			height = 100;
		scrollPane1.setBounds(10, 210, 125, height);
		scrollPane1.setBorder(BorderFactory.createEmptyBorder());
		JPanel transitionsPane = new JPanel(new GridLayout(0, 1));

		JLabel uncontrollableLbl = new JLabel("uncontrollable:");
		uncontrollableLbl.setFont(normal);
		uncontrollableLbl.setHorizontalAlignment(SwingConstants.LEFT);
		transitionsPane.add(uncontrollableLbl);

		for (int i = 0; i < transitions.length; i++)
		{
			JCheckBox jrb = new JCheckBox(transitions[i]);
			jrb.setFont(normal);
			jrb.setName("transition_" + transitions[i]);
			componentMap.put(jrb.getName(), jrb);
			transitionsPane.add(jrb);
		}
		scrollPane1.setViewportView(transitionsPane);
		scrollPane1.setVisible(false);
		contentPane.add(scrollPane1);
		contentPane.revalidate();
		contentPane.repaint();
	}

	/**
	 * Initializes the domain pane.
	 */
	public void initDomainPane()
	{
		if (scrollPane2 != null)
		{
			contentPane.remove(scrollPane2);
			// for (Entry<String, Component> entry : componentMap.entrySet())
			// if (entry.getKey().startsWith("initial_")
			// || entry.getKey().startsWith("domainFrom_")
			// || entry.getKey().startsWith("domainTo_"))
			// componentMap.remove(entry.getKey());
		}
		if (vars.length < 1)
			return;

		scrollPane2 = new JScrollPane();
		int height = 115;
		if (vars.length == 1)
			height = 40;
		if (vars.length == 2)
			height = 60;
		if (vars.length == 3)
			height = 80;
		if (vars.length == 4)
			height = 100;
		scrollPane2.setBounds(159, 210, 170, height);
		scrollPane2.setBorder(BorderFactory.createEmptyBorder());

		JPanel domainPane = new JPanel(new GridLayout(0, 4));

		JLabel variable = new JLabel();
		variable.setHorizontalAlignment(SwingConstants.CENTER);
		variable.setFont(normal);
		domainPane.add(variable);

		JLabel init = new JLabel("initial:");
		init.setHorizontalAlignment(SwingConstants.CENTER);
		init.setFont(normal);
		domainPane.add(init);

		JLabel from = new JLabel("from:");
		from.setHorizontalAlignment(SwingConstants.CENTER);
		from.setFont(normal);
		domainPane.add(from);

		JLabel to = new JLabel("to:");
		to.setHorizontalAlignment(SwingConstants.CENTER);
		to.setFont(normal);
		domainPane.add(to);

		for (int i = 0; i < vars.length; i++)
		{
			JLabel name = new JLabel(vars[i] + ": ");
			name.setFont(normal);
			name.setHorizontalAlignment(SwingConstants.RIGHT);

			JTextField initial = new JTextField();
			initial.setName("initial_" + vars[i]);
			initial.setFont(normal);
			componentMap.put(initial.getName(), initial);

			JTextField domainFrom = new JTextField();
			domainFrom.setName("domainFrom_" + vars[i]);
			domainFrom.setFont(normal);
			componentMap.put(domainFrom.getName(), domainFrom);

			JTextField domainTo = new JTextField();
			domainTo.setName("domainTo_" + vars[i]);
			domainTo.setFont(normal);
			componentMap.put(domainTo.getName(), domainTo);

			domainPane.add(name);
			domainPane.add(initial);
			domainPane.add(domainFrom);
			domainPane.add(domainTo);
		}

		scrollPane2.setViewportView(domainPane);
		scrollPane2.setVisible(false);
		contentPane.add(scrollPane2);
		contentPane.revalidate();
		contentPane.repaint();
	}

	/**
	 * Launch the application.
	 */
	public static void main(String[] args)
	{
		EventQueue.invokeLater(new Runnable()
		{
			public void run()
			{
				try
				{
					Gui frame = new Gui();
					frame.setVisible(true);
				}
				catch (Exception e)
				{
					e.printStackTrace();
				}
			}
		});
	}
}
